import ButtonLink from '@components/atoms/buttons/button-link'; import Link from '@components/atoms/links/link'; import ResponsiveImage from '@components/molecules/images/responsive-image'; import Sharing from '@components/organisms/widgets/sharing'; import { getLayout } from '@components/templates/layout/layout'; import PageLayout, { type PageLayoutProps, } from '@components/templates/page/page-layout'; import { getAllArticlesSlugs, getArticleBySlug, } from '@services/graphql/articles'; import { getPostComments } from '@services/graphql/comments'; import styles from '@styles/pages/article.module.scss'; import { type Article, type Comment, type NextPageWithLayout, } from '@ts/types/app'; import { loadTranslation, type Messages } from '@utils/helpers/i18n'; import useAddPrismClassAttr from '@utils/hooks/use-add-prism-class-attr'; import useBreadcrumb from '@utils/hooks/use-breadcrumb'; import usePrismPlugins, { type PrismPlugin, } from '@utils/hooks/use-prism-plugins'; import useReadingTime from '@utils/hooks/use-reading-time'; import useSettings from '@utils/hooks/use-settings'; import { GetStaticPaths, GetStaticProps } from 'next'; import Head from 'next/head'; import { useRouter } from 'next/router'; import Script from 'next/script'; import { ParsedUrlQuery } from 'querystring'; import { HTMLAttributes } from 'react'; import { useIntl } from 'react-intl'; import { Blog, BlogPosting, Graph, WebPage } from 'schema-dts'; import useSWR from 'swr'; type ArticlePageProps = { comments: Comment[]; post: Article; translation: Messages; }; /** * Article page. */ const ArticlePage: NextPageWithLayout = ({ comments, post, }) => { const { content, id, intro, meta, slug, title } = post; const { author, commentsCount, cover, dates, seo, thematics, topics, wordsCount, } = meta; const { data } = useSWR(() => id, getPostComments, { fallbackData: comments, }); const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumb({ title, url: `/article/${slug}`, }); const readingTime = useReadingTime(wordsCount || 0, true); const headerMeta: PageLayoutProps['headerMeta'] = { author: author?.name, publication: { date: dates.publication }, update: dates.update ? { date: dates.update } : undefined, readingTime, thematics: thematics && thematics.map((thematic) => ( {thematic.name} )), }; const intl = useIntl(); const footerMetaLabel = intl.formatMessage({ defaultMessage: 'Read more articles about:', description: 'ArticlePage: footer topics list label', id: '50xc4o', }); const footerMeta: PageLayoutProps['footerMeta'] = { custom: topics && { label: footerMetaLabel, value: topics.map((topic) => { return ( {topic.logo && ( )}{' '} {topic.name} ); }), }, }; const { website } = useSettings(); const { asPath } = useRouter(); const pageUrl = `${website.url}${asPath}`; const pagePublicationDate = new Date(dates.publication); const pageUpdateDate = dates.update ? new Date(dates.update) : undefined; const webpageSchema: WebPage = { '@id': `${pageUrl}`, '@type': 'WebPage', breadcrumb: { '@id': `${website.url}/#breadcrumb` }, lastReviewed: dates.update, name: seo.title, description: seo.description, reviewedBy: { '@id': `${website.url}/#branding` }, url: `${pageUrl}`, isPartOf: { '@id': `${website.url}`, }, }; const blogSchema: Blog = { '@id': `${website.url}/#blog`, '@type': 'Blog', blogPost: { '@id': `${website.url}/#article` }, isPartOf: { '@id': `${pageUrl}`, }, license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', }; const blogPostSchema: BlogPosting = { '@id': `${website.url}/#article`, '@type': 'BlogPosting', name: title, description: intro, articleBody: content, author: { '@id': `${website.url}/#branding` }, commentCount: commentsCount, copyrightYear: pagePublicationDate.getFullYear(), creator: { '@id': `${website.url}/#branding` }, dateCreated: pagePublicationDate.toISOString(), dateModified: pageUpdateDate && pageUpdateDate.toISOString(), datePublished: pagePublicationDate.toISOString(), discussionUrl: `${pageUrl}/#comments`, editor: { '@id': `${website.url}/#branding` }, headline: title, image: cover?.src, inLanguage: website.locales.default, isPartOf: { '@id': `${website.url}/blog`, }, license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', mainEntityOfPage: { '@id': `${pageUrl}` }, thumbnailUrl: cover?.src, }; const schemaJsonLd: Graph = { '@context': 'https://schema.org', '@graph': [webpageSchema, blogSchema, blogPostSchema], }; /** * Convert the comments list to the right format. * * @param {Comment[]} list - The comments list. * @returns {PageLayoutProps['comments']} - The formatted comments list. */ const getCommentsList = (list: Comment[]): PageLayoutProps['comments'] => { return list.map((comment) => { const { content: commentBody, id: commentId, meta: commentMeta, parentId, replies, } = comment; const { author: commentAuthor, date } = commentMeta; const { name, avatar, website: authorUrl } = commentAuthor; return { author: { name, avatar: avatar!.src, url: authorUrl }, content: commentBody, id: commentId, publication: date, child: getCommentsList(replies), parentId, }; }); }; const prismPlugins: PrismPlugin[] = ['command-line', 'line-numbers']; const { pluginsAttribute, pluginsClassName } = usePrismPlugins(prismPlugins); useAddPrismClassAttr({ attributes: { 'data-filter-output': '#output#"', }, classNames: pluginsClassName, }); return ( <> {seo.title}